Skip to content

feat: add evaluate_flags/2 API for single-call flag evaluation#103

Open
dmarticus wants to merge 3 commits intomainfrom
posthog-code/elixir-evaluate-flags-api
Open

feat: add evaluate_flags/2 API for single-call flag evaluation#103
dmarticus wants to merge 3 commits intomainfrom
posthog-code/elixir-evaluate-flags-api

Conversation

@dmarticus
Copy link
Copy Markdown
Contributor

@dmarticus dmarticus commented Apr 27, 2026

Summary

{:ok, snapshot} = PostHog.FeatureFlags.evaluate_flags("user-123")

if PostHog.FeatureFlags.Evaluations.enabled?(snapshot, "new-dashboard") do
  render_new_dashboard()
end

PostHog.FeatureFlags.set_in_context(snapshot)
PostHog.capture("page_viewed", %{distinct_id: "user-123"})

One /flags call, multiple flag checks, plus event enrichment from the same snapshot — replacing N check/3 round-trips.

  • New PostHog.FeatureFlags.evaluate_flags/2 returns an Evaluations snapshot. Pass flag_keys: [...] to scope the underlying request body (forwarded as flag_keys_to_evaluate).
  • Snapshot exposes enabled?/2, get_flag/2, get_flag_payload/2, only/2, keys/1, event_properties/1. enabled?/2 and get_flag/2 fire $feature_flag_called with full metadata; get_flag_payload/2 doesn't.
  • set_in_context/2 copies the snapshot's $feature/<key> and $active_feature_flags into the per-process context so subsequent PostHog.capture/3 calls pick them up — no new capture arity needed.
  • %PostHog.FeatureFlags.Result{} now carries :id, :version, :reason, :request_id, :evaluated_at, and :errors_while_computing. Both paths attach the matching $feature_flag_* properties (including $feature_flag_error: "errors_while_computing_flags") when the response provides them. Snapshot path also fires $feature_flag_error: "flag_missing" for unknown keys, combined comma-style with the response-level error when both apply.
  • check/3, check!/3, get_feature_flag_result/4, and get_feature_flag_result!/4 are now @deprecated (compile-time warnings + admonitions in @doc). They continue to return the same values; removal is planned for the next major.

RFC: https://github.com/PostHog/requests-for-comments-internal/pull/1020. Reference implementations: posthog-python#539, posthog-js#3476.

Design decisions

  • No capture(flags: snapshot) argument. Elixir's SDK already propagates $feature/* through the per-process context that check/3 writes to and capture/3 reads back. set_in_context/2 slots into that mechanism instead of fighting it.
  • Snapshot is pure-functional, not Agent-backed. Mutable access tracking would require an Agent or threading a new snapshot through every query — both awkward in Elixir. only/2 (filter by explicit keys) is enough; only_accessed/1 from the cross-SDK API is dropped.
  • Predicate is enabled?/2, not is_enabled/2. Idiomatic Elixir; is_* is reserved for guard-safe predicates and Credo flags it.
  • Both paths share build_result/3 and log_feature_flag_usage/3. Single source of truth for $feature_flag_called properties — including the rich metadata and the new $feature_flag_error codes.
  • Snapshot fires flag_missing events; existing single-flag path keeps its {:error, ...} / {:ok, nil} contract for missing flags. Mirrors posthog-python's per-call telemetry from a snapshot while not changing existing check/3 behavior.
  • No dedup cache. The Elixir SDK has no per-distinct_id dedup for $feature_flag_called; both paths fire on every access. Adding the cache is a separate behavior change to existing call sites and lands in its own PR.
  • No new config option. only/2 silently drops unknown keys (matching Map.take/2), so there's no warning to silence.

Out of scope (future PRs)

  • Per-distinct_id dedup cache for $feature_flag_called events, applied to both paths.
  • Local-evaluation poller, with locally_evaluated, $feature_flag_reason: "Evaluated locally", and $feature_flag_definitions_loaded_at plumbed through the snapshot.
  • only_accessed/1 if users ask for it.
  • Phase 3: removal of the deprecated single-flag methods in the next major.

227/227 tests pass, format + Credo strict clean. Compile warnings on the four deprecated functions are expected — they mark call sites that need migrating.


Created with PostHog Code

Introduce PostHog.FeatureFlags.evaluate_flags/2 returning an Evaluations
snapshot that powers branching across multiple flags and event enrichment
from a single /flags request. Pairs with PostHog.FeatureFlags.set_in_context/2
for the idiomatic Elixir capture-enrichment flow via the existing per-process
context, and with Evaluations.event_properties/1 for explicit one-off attach.

%PostHog.FeatureFlags.Result{} gains :id, :version, :reason, :request_id, and
:evaluated_at. The existing check/3, check!/3, and get_feature_flag_result/4
paths now attach $feature_flag_id, $feature_flag_version, $feature_flag_reason,
and $feature_flag_request_id to $feature_flag_called events when the response
provides them - needed for experiment exposure tracking.

Generated-By: PostHog Code
Task-Id: c6aa804c-618a-4229-b6a3-dc8c9ccff778
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 27, 2026

posthog-elixir Compliance Report

Date: 2026-04-29 21:17:56 UTC
Duration: 105969ms

✅ All Tests Passed!

30/30 tests passed


Capture Tests

29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 609ms
Format Validation.Event Has Uuid 609ms
Format Validation.Event Has Lib Properties 609ms
Format Validation.Distinct Id Is String 609ms
Format Validation.Token Is Present 609ms
Format Validation.Custom Properties Preserved 609ms
Format Validation.Event Has Timestamp 609ms
Retry Behavior.Retries On 503 5614ms
Retry Behavior.Does Not Retry On 400 2612ms
Retry Behavior.Does Not Retry On 401 2612ms
Retry Behavior.Respects Retry After Header 5614ms
Retry Behavior.Implements Backoff 15625ms
Retry Behavior.Retries On 500 5614ms
Retry Behavior.Retries On 502 5614ms
Retry Behavior.Retries On 504 5615ms
Retry Behavior.Max Retries Respected 15625ms
Deduplication.Generates Unique Uuids 618ms
Deduplication.Preserves Uuid On Retry 5615ms
Deduplication.Preserves Uuid And Timestamp On Retry 10617ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 5616ms
Deduplication.No Duplicate Events In Batch 613ms
Deduplication.Different Events Have Different Uuids 613ms
Compression.Sends Gzip When Enabled 609ms
Batch Format.Uses Proper Batch Structure 609ms
Batch Format.Flush With No Events Sends Nothing 607ms
Batch Format.Multiple Events Batched Together 613ms
Error Handling.Does Not Retry On 403 2621ms
Error Handling.Does Not Retry On 413 2613ms
Error Handling.Retries On 408 5614ms

Feature_Flags Tests

1/1 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id 8ms

@dmarticus dmarticus marked this pull request as ready for review April 27, 2026 22:12
@dmarticus dmarticus requested review from a team and rafaeelaudibert as code owners April 27, 2026 22:12
Address PR feedback (mirrors posthog-python#539's 95eb1e9 fix). The /flags
response carries `errorsWhileComputingFlags` to signal partial-evaluation
failure, but events fired from both the existing single-flag path and the
new snapshot path were dropping it.

%PostHog.FeatureFlags.Result{} gains :errors_while_computing, populated from
the response body. log_feature_flag_usage/3 now builds a comma-joined
$feature_flag_error property when errors are present.

The snapshot path also fires $feature_flag_called for unknown flags with
$feature_flag_error: "flag_missing" — useful telemetry for "user asked for
a flag that does not exist" — combined with errors_while_computing_flags
when both apply ("errors_while_computing_flags,flag_missing"). The existing
single-flag path is unchanged for missing flags (still returns nil/error).

Generated-By: PostHog Code
Task-Id: c6aa804c-618a-4229-b6a3-dc8c9ccff778
Adds @deprecated to the four legacy single-flag entry points and their
auto-generated lower-arity overloads, pointing users at evaluate_flags/2 +
Evaluations. Compile-time warnings emit on every call site; functions
continue to return the same values. Removal is planned for the next major.

Each function also gains a "Deprecated" admonition in its @doc so generated
HexDocs surface the migration guidance.

Generated-By: PostHog Code
Task-Id: c6aa804c-618a-4229-b6a3-dc8c9ccff778
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant